﻿using Dapper;
using Dommel;
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Loong.Dapper.Filters.Action;
using Loong.Dapper.Filters.Query;
using Loong.Domain.Entities;

namespace Loong.Dapper.DbAccessors
{
    public class DapperDbAccessorBase<TEntity, TPrimaryKey> : IDapperDbAccessor<TEntity, TPrimaryKey> where TEntity : class, IEntity<TPrimaryKey>
    {
        private readonly IActiveTransactionProvider _activeTransactionProvider;
        private readonly IDapperQueryFilterExecuter _dapperQueryFilterExecuter;
        private readonly IDapperActionFilterExecuter _dapperActionFilterExecuter;

        public DapperDbAccessorBase(
            IActiveTransactionProvider activeTransactionProvider,
            IDapperQueryFilterExecuter dapperQueryFilterExecuter,
            IDapperActionFilterExecuter dapperActionFilterExecuter)
        {
            _activeTransactionProvider = activeTransactionProvider;
            _dapperQueryFilterExecuter = dapperQueryFilterExecuter;
            _dapperActionFilterExecuter = dapperActionFilterExecuter;
        }

        public virtual DbConnection Connection
        {
            get { return _activeTransactionProvider.GetConnection(); }
        }

        public virtual DbTransaction Transaction
        {
            get { return _activeTransactionProvider.GetTransaction(); }
        }

        public virtual TEntity Single(TPrimaryKey id)
        {
            return Single(CreateEqualityExpressionForId(id));
        }

        public virtual TEntity Single(Expression<Func<TEntity, bool>> predicate)
        {
            predicate = _dapperQueryFilterExecuter.ExecuteFilter<TEntity, TPrimaryKey>(predicate);
            return Connection.Select(predicate, Transaction).Single();
        }

        public virtual Task<TEntity> SingleAsync(TPrimaryKey id)
        {
            return SingleAsync(CreateEqualityExpressionForId(id));
        }

        public virtual async Task<TEntity> SingleAsync(Expression<Func<TEntity, bool>> predicate)
        {
            predicate = _dapperQueryFilterExecuter.ExecuteFilter<TEntity, TPrimaryKey>(predicate);
            return (await Connection.SelectAsync(predicate, Transaction)).Single();
        }

        public virtual TEntity Get(TPrimaryKey id)
        {
            TEntity item = FirstOrDefault(id);
            if (item == null)
            {
                throw new EntityNotFoundException(typeof(TEntity), id);
            }

            return item;
        }

        public virtual async Task<TEntity> GetAsync(TPrimaryKey id)
        {
            TEntity item = await FirstOrDefaultAsync(id);
            if (item == null)
            {
                throw new EntityNotFoundException(typeof(TEntity), id);
            }

            return item;
        }

        public virtual TEntity FirstOrDefault(TPrimaryKey id)
        {
            return FirstOrDefault(CreateEqualityExpressionForId(id));
        }

        public virtual TEntity FirstOrDefault(Expression<Func<TEntity, bool>> predicate)
        {
            predicate = _dapperQueryFilterExecuter.ExecuteFilter<TEntity, TPrimaryKey>(predicate);
            return Connection.FirstOrDefault(predicate, Transaction);
        }

        public virtual Task<TEntity> FirstOrDefaultAsync(TPrimaryKey id)
        {
            return FirstOrDefaultAsync(CreateEqualityExpressionForId(id));
        }

        public virtual Task<TEntity> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate)
        {
            predicate = _dapperQueryFilterExecuter.ExecuteFilter<TEntity, TPrimaryKey>(predicate);
            return Connection.FirstOrDefaultAsync(predicate, Transaction);
        }

        public virtual IEnumerable<TEntity> GetAll()
        {
            var predicate = _dapperQueryFilterExecuter.ExecuteFilter<TEntity, TPrimaryKey>(null);
            return Connection.Select(predicate, Transaction);
        }

        public virtual IEnumerable<TEntity> GetAll(Expression<Func<TEntity, bool>> predicate)
        {
            predicate = _dapperQueryFilterExecuter.ExecuteFilter<TEntity, TPrimaryKey>(predicate);
            return Connection.Select(predicate, Transaction);
        }

        public virtual Task<IEnumerable<TEntity>> GetAllAsync()
        {
            var predicate = _dapperQueryFilterExecuter.ExecuteFilter<TEntity, TPrimaryKey>(null);
            return Connection.SelectAsync(predicate, Transaction);
        }

        public virtual Task<IEnumerable<TEntity>> GetAllAsync(Expression<Func<TEntity, bool>> predicate)
        {
            predicate = _dapperQueryFilterExecuter.ExecuteFilter<TEntity, TPrimaryKey>(predicate);
            return Connection.SelectAsync(predicate, Transaction);
        }

        public virtual long Count(Expression<Func<TEntity, bool>> predicate)
        {
            predicate = _dapperQueryFilterExecuter.ExecuteFilter<TEntity, TPrimaryKey>(predicate);
            return Connection.Count(predicate, Transaction);
        }

        public virtual Task<long> CountAsync(Expression<Func<TEntity, bool>> predicate)
        {
            predicate = _dapperQueryFilterExecuter.ExecuteFilter<TEntity, TPrimaryKey>(predicate);
            return Connection.CountAsync(predicate, Transaction);
        }

        public virtual IEnumerable<TEntity> Query(string query, object parameters = null)
        {
            return Connection.Query<TEntity>(query, parameters, Transaction);
        }

        public virtual IEnumerable<TAny> Query<TAny>(string query, object parameters = null) where TAny : class
        {
            return Connection.Query<TAny>(query, parameters, Transaction);
        }

        public virtual Task<IEnumerable<TEntity>> QueryAsync(string query, object parameters = null)
        {
            return Connection.QueryAsync<TEntity>(query, parameters, Transaction);
        }

        public virtual Task<IEnumerable<TAny>> QueryAsync<TAny>(string query, object parameters = null) where TAny : class
        {
            return Connection.QueryAsync<TAny>(query, parameters, Transaction);
        }

        public virtual int Execute(string query, object parameters = null)
        {
            return Connection.Execute(query, parameters, Transaction);
        }

        public virtual Task<int> ExecuteAsync(string query, object parameters = null)
        {
            return Connection.ExecuteAsync(query, parameters, Transaction);
        }

        public virtual void Insert(TEntity entity)
        {
            InsertAndGetId(entity);
        }

        public virtual Task InsertAsync(TEntity entity)
        {
            return InsertAndGetIdAsync(entity);
        }

        public virtual TPrimaryKey InsertAndGetId(TEntity entity)
        {
            _dapperActionFilterExecuter.ExecuteCreationAuditFilter<TEntity, TPrimaryKey>(entity);
            TPrimaryKey primaryKey = (TPrimaryKey)Convert.ChangeType(Connection.Insert(entity, Transaction), typeof(TPrimaryKey));
            entity.Id = primaryKey;
            return primaryKey;
        }

        public virtual async Task<TPrimaryKey> InsertAndGetIdAsync(TEntity entity)
        {
            _dapperActionFilterExecuter.ExecuteCreationAuditFilter<TEntity, TPrimaryKey>(entity);
            TPrimaryKey primaryKey = (TPrimaryKey)Convert.ChangeType(await Connection.InsertAsync(entity, Transaction), typeof(TPrimaryKey));
            entity.Id = primaryKey;
            return primaryKey;
        }

        public virtual void Update(TEntity entity)
        {
            _dapperActionFilterExecuter.ExecuteModificationAuditFilter<TEntity, TPrimaryKey>(entity);
            Connection.Update(entity, Transaction);
        }

        public virtual Task UpdateAsync(TEntity entity)
        {
            _dapperActionFilterExecuter.ExecuteModificationAuditFilter<TEntity, TPrimaryKey>(entity);
            return Connection.UpdateAsync(entity, Transaction);
        }

        public virtual void Delete(TEntity entity)
        {
            if (entity is ISoftDelete)
            {
                _dapperActionFilterExecuter.ExecuteDeletionAuditFilter<TEntity, TPrimaryKey>(entity);
                Connection.Update(entity, Transaction);
            }
            else
            {
                Connection.Delete(entity, Transaction);
            }
        }

        public virtual void Delete(Expression<Func<TEntity, bool>> predicate)
        {
            IEnumerable<TEntity> items = GetAll(predicate);
            foreach (TEntity entity in items)
            {
                Delete(entity);
            }
        }

        public virtual Task DeleteAsync(TEntity entity)
        {
            if (entity is ISoftDelete)
            {
                _dapperActionFilterExecuter.ExecuteDeletionAuditFilter<TEntity, TPrimaryKey>(entity);
                return Connection.UpdateAsync(entity, Transaction);
            }
            else
            {
                return Connection.DeleteAsync(entity, Transaction);
            }
        }

        public virtual async Task DeleteAsync(Expression<Func<TEntity, bool>> predicate)
        {
            IEnumerable<TEntity> items = await GetAllAsync(predicate);
            foreach (TEntity entity in items)
            {
                await DeleteAsync(entity);
            }
        }

        protected Expression<Func<TEntity, bool>> CreateEqualityExpressionForId(TPrimaryKey id)
        {
            ParameterExpression lambdaParam = Expression.Parameter(typeof(TEntity));

            BinaryExpression lambdaBody = Expression.Equal(
                Expression.PropertyOrField(lambdaParam, "Id"),
                Expression.Constant(id, typeof(TPrimaryKey))
            );

            return Expression.Lambda<Func<TEntity, bool>>(lambdaBody, lambdaParam);
        }
    }

    public class DapperDbAccessorBase<TEntity> : DapperDbAccessorBase<TEntity, int>, IDapperDbAccessor<TEntity> where TEntity : class, IEntity<int>
    {
        public DapperDbAccessorBase(
            IActiveTransactionProvider activeTransactionProvider,
            IDapperQueryFilterExecuter dapperQueryFilterExecuter,
            IDapperActionFilterExecuter dapperActionFilterExecuter)
            : base(activeTransactionProvider, dapperQueryFilterExecuter, dapperActionFilterExecuter)
        {
        }
    }
}
